# -*- coding: utf-8 -*-
import uuid

from fastapi import Request
from loguru import logger
from pydantic import BaseModel
from typing import List, Optional

from schemas.response_entity import JsonModel
from db.repository.user import db_get_groups_for_superadmin, db_get_user_group_ids
from db.repository.knowledge import db_delete_kb_file_asso, db_get_all_files_by_params, db_get_all_kbs, db_get_files_not_in_kb, db_get_kbs_by_user, db_get_files_in_kb, db_get_kb_by_id, \
    db_get_asso_by_kb_file, db_get_files_in_kb_by_params, db_add_kb_file_asso, \
    db_get_kb_by_name, db_create_kb, db_update_kb_name, db_delete_files_in_kb, \
    db_update_kb_groups, db_delete_kb_by_id, db_get_files_not_in_kb_by_params
from db.repository.qa_file import db_get_qa_file_by_id
from db.repository.dual_control_file import db_get_dc_file_by_id
from db.repository.emergency_file import db_get_ep_file_by_id
from db.repository.accident_case_file import db_get_ac_file_by_id
from db.repository.audio_file import db_get_audio_by_id


file_method_mapping = {
    '1': db_get_qa_file_by_id,
    '2': db_get_dc_file_by_id,
    '3': db_get_ep_file_by_id,
    '4': db_get_ac_file_by_id,
    # '5': db_get_audio_by_id,
}


class KnowledgeAddEntity(BaseModel):
    name: str
    group_ids: List[int] = None
    token: str
    file_ids: List[uuid.UUID] = None
    kb_type: int = 1


class KnowledgeUpdateEntity(BaseModel):
    kb_id: uuid.UUID
    name: str
    token: str


class AddFilesToKb(BaseModel):
    token: str
    knowledge_id: Optional[uuid.UUID]
    file_ids: List[uuid.UUID]


class DeleteEntity(BaseModel):
    token: str
    knowledge_id: uuid.UUID
    file_id: uuid.UUID


class UEntity(BaseModel):
    token: str
    kb_id: uuid.UUID
    group_ids: List[int] = None


class KnowledgeFilesQueryPayload(BaseModel):
    knowledge_id: Optional[uuid.UUID] = None
    kb_type: int = 1
    name: str = ""
    applicable_object: Optional[List[int]] = None
    knowledge_type: Optional[List[int]] = None
    applicable_area: Optional[List[int]] = None
    element_classification: Optional[List[int]] = None
    page: int = 1
    page_size: int = 50
    # 查询模式: in-指定知识库内(默认); all-全部文件; exclude-排除指定知识库
    mode: Optional[str] = "in"


# 知识库的列表
# 功能：根据用户角色返回可见的知识库列表（管理员返回全部，普通用户返回其有权限的）
async def show_knowledge_list_all(kb_type: int = 1):
    # 权限判断并查询列表
    kb_list = db_get_all_kbs(kb_type)
    return JsonModel(code=200, data=[i.to_json() for i in kb_list], msg="Success").to_response()


# 知识库的列表
# 功能：根据用户角色返回可见的知识库列表（管理员返回全部，普通用户返回其有权限的）
async def show_knowledge_list(request: Request, kb_type: int = 1):
    user = request.state.user
    # 权限判断并查询列表
    if user.check_admin_role():
        kb_list = db_get_all_kbs(kb_type)
        return JsonModel(code=200, data=[i.to_json() for i in kb_list], msg="Success").to_response()
    else:
        user_group_ids = db_get_user_group_ids(user)
        kb_list = db_get_kbs_by_user(user, user_group_ids, kb_type)
        return JsonModel(code=200, data=[i.to_json() for i in kb_list], msg="Success").to_response()


# 知识库文件的列表
# 功能：仅管理员可查看指定知识库下的文件列表，包含权限与存在性校验
async def show_knowledge_file_list(request: Request, knowledge_id: uuid.UUID, page: int = 1, page_size: int = 50, name: str = ""):
    user = request.state.user
    kb = db_get_kb_by_id(knowledge_id)
    if not kb:
        return JsonModel(code=404, data=None, msg=f"知识库不存在,请更换知识库").to_response()
    
    offset = (page - 1) * page_size
    kb_list, total = db_get_files_in_kb(knowledge_id, kb.kb_type, name_like=name or None, offset=offset, limit=page_size) or ([], 0)
    return JsonModel(
        code=200, 
        data={
            "items": [i.to_json() for i in kb_list],
            "total": total,
            "page": page,
            "page_size": page_size
        }, 
        msg="Success"
    ).to_response()


# 查询文件接口
# 功能：按名称、适用对象、知识类型、适用地域、要素分类多条件检索知识库文件
async def list_knowledge_files_query(request: Request, payload: KnowledgeFilesQueryPayload):
    """
    功能：按名称、适用对象、知识类型、适用地域、要素分类多条件检索知识库文件
    查询模式: in-指定知识库内(默认); all-全部文件; exclude-除了指定知识库内文件的所有文件中检索
    """
    user = request.state.user
    
    mode = (payload.mode or "in").lower()
    if payload.page <= 0 or payload.page_size <= 0:
        return JsonModel(code=400, data=None, msg="分页参数错误, 请检查 page 与 page_size").to_response()

    offset = (payload.page - 1) * payload.page_size

    # 全量文件标签检索（不限定知识库）
    if mode == "all":
        if str(payload.kb_type) != "1":
            return JsonModel(code=400, data=None, msg="暂不支持此知识库类型的全量检索").to_response()
        files, total = db_get_all_files_by_params(
            payload.name,
            payload.applicable_object,
            payload.knowledge_type,
            payload.applicable_area,
            payload.element_classification,
            offset=offset,
            limit=payload.page_size
        )
    # 指定知识库内文件检索
    elif mode == "in":
        if payload.knowledge_id is None:
            return JsonModel(code=400, data=None, msg="知识库ID不能为空").to_response()
        kb = db_get_kb_by_id(payload.knowledge_id)
        if kb is None:
            return JsonModel(code=404, data=None, msg=f"知识库不存在,请更换知识库").to_response()
        if not kb.check_permit(user, None):
            return JsonModel(code=401, data=None, msg=f"用户无权限操作此知识库").to_response()

        files, total = db_get_files_in_kb_by_params(
            payload.knowledge_id,
            payload.name,
            payload.applicable_object,
            payload.knowledge_type,
            payload.applicable_area,
            payload.element_classification,
            offset=offset,
            limit=payload.page_size
        )
    # 排除指定知识库内文件检索
    elif mode == "exclude":
        if payload.knowledge_id is None:
            return JsonModel(code=400, data=None, msg="知识库ID不能为空").to_response()
        kb = db_get_kb_by_id(payload.knowledge_id)
        if kb is None:
            return JsonModel(code=404, data=None, msg=f"知识库不存在,请更换知识库").to_response()
        if not kb.check_permit(user, None):
            return JsonModel(code=401, data=None, msg=f"用户无权限操作此知识库").to_response()

        files, total = db_get_files_not_in_kb_by_params(
            payload.knowledge_id,
            payload.name,
            payload.applicable_object,
            payload.knowledge_type,
            payload.applicable_area,
            payload.element_classification,
            offset=offset,
            limit=payload.page_size
        )
    else:
        return JsonModel(code=400, data=None, msg=f"查询模式错误,请更换查询模式").to_response()
    return JsonModel(
        code=200, 
        data={
            "items": [i.to_json() for i in files],
            "total": total,
            "page": payload.page,
            "page_size": payload.page_size
        }, 
        msg="Success"
    ).to_response()


# 创建知识库
# 功能：创建新的知识库（可选同时关联文件），含权限、重名与用户组校验
async def add_knowledge_base(request: Request, payload: KnowledgeAddEntity):
    user = request.state.user

    # 检查用户权限
    if not user.check_admin_role():
        return JsonModel(code=401, data=None, msg=f"用户无权限").to_response()

    # 检查知识库是否存在
    kb = db_get_kb_by_name(payload.name)
    if kb:
        return JsonModel(code=403, data=None, msg=f"知识库:{payload.name}已存在,请更换知识库名").to_response()

    # 用户组校验（默认组ID=1）
    payload.group_ids = payload.group_ids or [1]
    valid_group_ids = {group.id for group in db_get_groups_for_superadmin()}
    if invalid_groups := set(payload.group_ids) - valid_group_ids:
        return JsonModel(code=404, data=None, msg=f"无效的用户组ID: {', '.join(map(str, invalid_groups))}").to_response()

    # 创建知识库
    try:
        new_kb = db_create_kb(payload.name, user.id, payload.group_ids, payload.kb_type)
        logger.info(f"new_kb: {new_kb}")
        if not new_kb:
            raise RuntimeError("数据库创建失败")
    except Exception as e:
        logger.error(f"知识库创建失败: {e}")
        return JsonModel(code=500, data=None, msg="知识库创建失败").to_response()

    if not payload.file_ids:
        return JsonModel(code=200, data=None, msg=f"知识库:{payload.name}创建成功").to_response()

    # 检查文件是否存在
    failed_files = []
    for file_id in payload.file_ids:
        file = db_get_qa_file_by_id(file_id)
        if not file:
            failed_files.append(str(file_id))
    if failed_files:
        return JsonModel(code=400, data=None, msg=f"文件{', '.join(failed_files)}不存在,请更换文件").to_response()

    # 关联文件到知识库
    for file_id in payload.file_ids:
        db_add_kb_file_asso(new_kb.id, file_id)
    return JsonModel(code=200, data=None, msg=f"知识库 [{payload.name}] 创建成功").to_response()


# 更新知识库名字
# 功能：管理员修改指定知识库的名称
async def update_knowledge_name(request: Request, payload: KnowledgeUpdateEntity):
    user = request.state.user
    
    # 检查用户权限
    if not user.check_admin_role():
        return JsonModel(code=401, data=None, msg="用户无权限").to_response()
    
    # 查询知识库
    kb = db_get_kb_by_id(payload.kb_id)
    if not kb:
        return JsonModel(code=404, data=None, msg=f"知识库不存在,请更换知识库").to_response()
    
    # 更新知识库名字
    try:
        db_update_kb_name(payload.kb_id, payload.name)
        return JsonModel(code=200, data=None, msg="知识库名称修改成功").to_response()

    except Exception as e:
        logger.error(f"Error: {e}")
        return JsonModel(code=500, data=None, msg="数据库操作失败").to_response()


# 删除知识库 和 关联
# 功能：删除知识库及与其相关联的文件关系，仅管理员可执行
async def delete_knowledge(request: Request, kb_id: uuid.UUID):
    user = request.state.user
    try:
        # 权限校验
        if not user.check_admin_role():
            return JsonModel(code=401, data=None, msg=f"用户无权限").to_response()
        
        # 查询知识库
        kb = db_get_kb_by_id(kb_id)
        if not kb:
            return JsonModel(code=404, data=None, msg=f"未查询到知识库,请更换知识库名").to_response()
        
        # 删除知识库与文件关联
        del_asso_status = db_delete_files_in_kb(kb_id)
        if del_asso_status:
            # 删除知识库
            del_kb_status = db_delete_kb_by_id(kb_id)
            if not del_kb_status:
                return JsonModel(code=500, data=None, msg=f"删除知识库失败").to_response()
        return JsonModel(code=200, data=None, msg=f"知识库删除成功").to_response()
    except Exception as e:
        logger.error(e)
        return JsonModel(code=500, data=None, msg=f"知识库删除失败").to_response()


# 关联文件到知识库
# 功能：将多个文件关联到指定知识库，逐一校验存在性与权限
async def add_knowledge_files(request: Request, payload: AddFilesToKb):
    user = request.state.user
    try:
        # 1) 校验知识库存在性
        knowledge = db_get_kb_by_id(payload.knowledge_id)
        if knowledge is None:
            return JsonModel(code=400, data=None, msg=f"知识库未找到,请更换知识库").to_response()
        
        # 2) 校验用户权限
        if not knowledge.check_permit(user, None):
            return JsonModel(code=401, data=None, msg=f"用户无权限操作此知识库").to_response()
        
        # 3) 批量校验文件是否存在
        failed = []
        kb_type_str = str(knowledge.kb_type)
        for file_id in payload.file_ids:
            try:
                file = file_method_mapping[kb_type_str](file_id)
                if not file:
                    failed.append(str(file_id))
            except Exception as e:
                logger.error(f"检查文件 {file_id} 时发生错误: {str(e)}")
                failed.append(str(file_id))
        
        # 4) 若存在无效文件，提前返回
        if failed:
            return JsonModel(code=400, data=None, msg=f"文件{', '.join(failed)}不存在,请更换文件").to_response()
        
        # 5) 仅管理员可执行关联文件
        if user.is_admin:
            success_count = 0
            failed_files = []
            for file_id in payload.file_ids:
                try:
                    asso = db_add_kb_file_asso(payload.knowledge_id, file_id)
                    if asso:
                        success_count += 1
                    else:
                        failed_files.append(str(file_id))
                except Exception as e:
                    logger.error(f"关联文件失败 [file_id={file_id}]: {e}")
                    failed_files.append(str(file_id))
            
            msg = f"成功关联 {success_count} 个文件"
            if failed_files:
                msg += f"，失败 {len(failed_files)} 个文件: {', '.join(failed_files)}"
            return JsonModel(code=200, data=None, msg=msg).to_response()
        else:
            return JsonModel(code=401, data=None, msg=f"用户无权限").to_response()

    except Exception as e:
        logger.error(f"知识库关联文件失败: {e}")
        return JsonModel(code=500, data=None, msg=f"知识库关联文件失败：{str(e)}").to_response()


# 删除文件和知识库的关联
# 功能：从知识库中移除指定文件的关联，包含权限、存在性与关联性校验
async def delete_file_from_kb(request: Request, payload: DeleteEntity):
    try:
        # 1) 获取用户并校验权限
        user = request.state.user
        if not user.is_admin:
            return JsonModel(code=401, data=None, msg=f"用户无权限").to_response()
        
        # 2) 校验知识库存在性与权限
        kb = db_get_kb_by_id(payload.knowledge_id)
        if kb is None:
            return JsonModel(code=400, data=None, msg=f"知识库不存在,请更换知识库").to_response()
        if not kb.check_permit(user, None):
            return JsonModel(code=402, data=None, msg=f"用户无权限操作此知识库").to_response()
        
        # 3) 校验文件存在性
        file = file_method_mapping[str(kb.kb_type)](payload.file_id)
        if not file:
            return JsonModel(code=404, data=None, msg=f"文件不存在,请更换文件").to_response()
        
        # 4) 校验关联是否存在
        kb_file_mapping = db_get_asso_by_kb_file(payload.knowledge_id, str(payload.file_id))
        if not kb_file_mapping:
            return JsonModel(code=400, data=None, msg=f"该文件不在该知识库中").to_response()
        
        # 5) 删除关联
        try:
            db_delete_kb_file_asso(kb_file_mapping)
            return JsonModel(code=200, data=None, msg=f"知识库删除文件{file.name}成功").to_response()
        except Exception as e:
            logger.error(f"删除关联记录时发生错误: {e}")
            raise ValueError(f"数据库删除失败: {e}")

    except Exception as e:
        logger.error(e)
        return JsonModel(code=500, data=None, msg=f"知识库删除文件{payload.file_id}失败").to_response()


# 更新知识库的用户组权限
# 功能：更新知识库可见的用户组列表，仅管理员可执行
async def update_kb_group(request: Request, payload: UEntity):
    user = request.state.user
    if not user.check_admin_role():
        return JsonModel(code=401, data=None, msg=f"用户无权限").to_response()

    kb = db_get_kb_by_id(payload.kb_id)
    if kb is None:
        return JsonModel(code=400, data=None, msg=f"知识库不存在,请更换").to_response()

    # 组ID有效性校验（允许空列表）
    groups = db_get_groups_for_superadmin()
    group_ids = [i.id for i in groups]

    # 兼容空列表
    payload.group_ids = payload.group_ids or []
    for group_id in payload.group_ids:
        if group_id not in group_ids:
            return JsonModel(code=401, data=None, msg=f"用户组Id: {group_id}不存在").to_response()

    if db_update_kb_groups(payload.kb_id, payload.group_ids):
        return JsonModel(code=200, data=None, msg=f"更新用户组成功").to_response()
    else:
        return JsonModel(code=500, data=None, msg=f"更新用户组失败").to_response()

